/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "pymetadata_vector.hh"
#include "pymetadata.hh"
#include "pymetadata_global.hh"
#include "gendata_common.hh"
#include "util.hh"

// DEFINITIONS
#define Py_RETURN_TRUE_  return PyBool_FromLong(true)
#define Py_RETURN_FALSE_ return PyBool_FromLong(false)

// CLASS
CLASS_INIT(python_vector); // PYTHON_VECTOR

python_vector::python_vector()
{
    CLASS_CTOR(python_vector);
    ctor();
}

python_vector::~python_vector()
{
    CLASS_DTOR(python_vector);
    dtor();
}

void python_vector::ctor()
{
    pylist=NULL;
}

void python_vector::dtor()
{
    Py_XDECREF(pylist);
}

bool python_vector::get_info(python_metadata *pyobj, int *n, int *size, int **pos_type_metadata)
{
    // get member metadata
    python_metadata *container=pyobj->get_obj_container();
    python_type_metadata *container_type=container->get_python_type();
    int *data=container_type->get_data_of_member_at_location(pyobj->get_location());
    if(!data || !(*pos_type_metadata=gendata_resolve_pos_type(data, 2/*n_indirections*/)))
    {
	ERROR("internal: bad metadata: n_indir=2, container: %s, location: %d",
	      container_type->get_name(), pyobj->get_location());
	PyErr_SetString(PyExc_IndexError, "Internal vector error");
	return false;
    }

    // get n_vector and vector item size
    int location;
    location=gendata_get_data(data, GENDATA_LOCATION, 1); // relative location of n_vector
    *n=*(int*)((char*)(container->get_obj())+location);
    *size=gendata_get_data(*pos_type_metadata, GENDATA_SIZE);

    return true;
}

void *python_vector::get_closure(python_metadata *pyobj)
{
    python_metadata *container=pyobj->get_obj_container();
    python_type_metadata *container_type=container->get_python_type();
    int *data=container_type->get_data_of_member_at_location(pyobj->get_location());
    if(!data) return NULL;
    return container_type->get_closure_of_member_with_data(data);
}

PyObject *python_vector::get_list(PyObject *self, int only_i)
{
    python_metadata *pyobj=(python_metadata *)self;
    python_metadata *container=pyobj->get_obj_container();

    if(container)
    {
	// use data from container
	int n, size, *pos_type_metadata;
	if(!get_info(pyobj, &n, &size, &pos_type_metadata)) return NULL;

	// obtain member function
	md_c_to_python_func func;
	int type_metadata;
	if((type_metadata=*pos_type_metadata)<0 ||
	   !(func=pymdg.get_func_c_to_python(type_metadata)))
	{
	    pymdg.clear_error_string();
	    pymdg.add_error_string("internal: check get function for type %s",
				   type_metadata>=0?gendata_type_strings[type_metadata]:"unknown");
	    PyErr_SetString(PyExc_TypeError, pymdg.get_error_string());
	    return NULL;
	}
	void *type_pointer=(char*)(container->get_obj())+pyobj->get_location();

	// check if we only want an item
	if(only_i>=n)
	{
	    PyErr_SetString(PyExc_IndexError, "Index out of bounds");
	    return NULL;
	}

	// pointer to first item
	type_pointer=*(void**)type_pointer;

	if(only_i>=0)
	{
	    // only one item
	    type_pointer=(char*)type_pointer+only_i*size;
	    PyObject *obj=func(type_pointer, pos_type_metadata);
	    if(!obj)
	    {
		    // make an elaborated error string
		    PyObject *exception;
		    pymdg.clear_error_string();
		    pymdg.add_error_string("element at position %d: %s", only_i, pymdg.python_error_string(&exception));
		    PyErr_SetString(exception, pymdg.get_error_string());
		    return NULL;
	    }
	    return obj;
	}
	else
	{
	    // generate list
	    PyObject *list=PyList_New(n);
	    for(int i=0; i<n; i++)
	    {
		PyObject *obj=func(type_pointer, pos_type_metadata);
		if(!obj)
		{
		    // delete list
		    Py_DECREF(list);

		    // make an elaborated error string
		    PyObject *exception;
		    pymdg.clear_error_string();
		    pymdg.add_error_string("element at position %d: %s", i, pymdg.python_error_string(&exception));
		    PyErr_SetString(exception, pymdg.get_error_string());
		    return NULL;
		}
		PyList_SetItem(list, i, obj);
		type_pointer=(char*)type_pointer+size;
	    }
	    return list;
	}
    }
    else
    {
	// use data from python_vector
	python_vector *vect=(python_vector*)pyobj->get_obj();
	if(only_i>=0)
	{
	    if(vect->pylist)
	    {
		PyObject *item;
		if((item=PyList_GetItem(vect->pylist, only_i))) Py_INCREF(item);
		return item;
	    }
	    else Py_RETURN_NONE;
	}
	else
	{
	    if(vect->pylist)
	    {
		Py_INCREF(vect->pylist);
		return vect->pylist;
	    }
	    return PyList_New(0);
	}
    }
}

int python_vector::set_list(PyObject *self, PyObject *list)
{
    python_metadata *pyobj=(python_metadata *)self;
    python_metadata *container=pyobj->get_obj_container();

    if(container)
    {
	ERROR_R0("set_list CONTAINER");
    }
    else
    {
	// use data from python_vector
	python_vector *vect=(python_vector*)pyobj->get_obj();
	PyObject *tmp=vect->pylist;
	Py_INCREF(list);
	vect->pylist=list;
	Py_XDECREF(tmp);
    }
    return 1;
}

PyObject *python_vector::__repr__(PyObject *self)
{
    PyObject *list;
    if(!(list=get_list(self))) list=PyList_New(0);
    PyObject *repr=PyObject_Repr(list);
    Py_DECREF(list);
    return repr;
}

PyObject *python_vector::__getitem__(PyObject *self, int i)
{
    if(i<0)
    {
	PyErr_SetString(PyExc_IndexError, "Negative index");
	return NULL;
    }
    return get_list(self, i);
}

int python_vector::__setitem__(PyObject *self, int i, PyObject *value)
{
    python_metadata *pyobj=(python_metadata *)self;
    python_metadata *container=pyobj->get_obj_container();

    if(container)
    {
	// TODO: do not set entire list, but just the specified item
	void *closure=get_closure(pyobj);
	if(!closure)
	{
	    ERROR("internal: !closure");
	    return -1;
	}

	PyObject *list;
	if(!(list=get_list(self)) || i>=PyList_Size(list) || i<0)
	{
	    Py_XDECREF(list);
	    PyErr_SetString(PyExc_IndexError, "Index out of bounds");
	    return -1;
	}
	if(PyList_SetItem(list, i, value)<0)
	{
	    Py_DECREF(list);
	    return -1;
	}
	Py_INCREF(value);

	int dev=python_type_metadata::pyset_member_vector_n_max((PyObject *)container, list, closure);
	Py_DECREF(list);
	return dev;
    }
    else
    {
	python_vector *vect=(python_vector*)pyobj->get_obj();
	return PyList_SetItem(vect->pylist, i, value);
    }
}

PyObject *python_vector::__getstate__(PyObject *self)
{
    PyObject *list;
    PyObject *dict=PyDict_New();
    if((list=get_list(self)))
    {
	PyDict_SetItemString(dict, "vector", list);
	Py_DECREF(list);
    }
    else
	PyErr_Clear(); // do NOT fail

    return dict;
}

PyObject *python_vector::__setstate__(PyObject *self, PyObject *args)
{
    PyObject *dict;
    if(args && PyArg_ParseTuple(args, (char*)"O", &dict) && PyDict_Check(dict))
    {
	PyObject *list;
	if((list=PyDict_GetItemString(dict, "vector")))
	    set_list(self, list);
	Py_RETURN_NONE;
    }
    if(!PyErr_Occurred()) PyErr_SetString(PyExc_TypeError, "A dictionary is required");
    return NULL;
}

PyObject *python_vector::__reduce__(PyObject *self)
{
    PyObject *tupla=PyTuple_New(3), *initargs=PyTuple_New(0);
    PyObject *dict=__getstate__(self), *init=(PyObject*)self->ob_type;
    Py_INCREF(init);
    PyTuple_SET_ITEM(tupla, 0, init);
    PyTuple_SET_ITEM(tupla, 1, initargs);
    PyTuple_SET_ITEM(tupla, 2, dict);
    return tupla;
}

PyObject *python_vector::__reduce_ex__(PyObject *self, PyObject *args)
{
    return __reduce__(self);
}

int python_vector::__len__(PyObject *self)
{
    python_metadata *pyobj=(python_metadata *)self;
    python_metadata *container=pyobj->get_obj_container();

    if(container)
    {
	// use data from container
	int n, size, *pos_type_metadata;
	if(!get_info(pyobj, &n, &size, &pos_type_metadata)) return -1;
	return n;
    }
    else
    {
	python_vector *vect=(python_vector*)pyobj->get_obj();
	if(vect->pylist) return PyList_Size(vect->pylist);
	return 0;
    }
}

int python_vector::__cmp__(PyObject *self, PyObject *value)
{
    // TODO for old versions
    return 0;
}

PyObject *python_vector::__richcmp__(PyObject *self, PyObject *value, int function)
{
    switch(function)
    {
    case 2: // ==
	{
	    if(self==value) Py_RETURN_TRUE_;
	    PyObject *list;
	    if(!(list=get_list(self))) Py_RETURN_FALSE_;
	    int ret=PyObject_Compare(list, value);
	    Py_DECREF(list);
	    if(ret) Py_RETURN_FALSE_; else Py_RETURN_TRUE_;
	}

    case 3: // !=
	{
	    if(self==value) Py_RETURN_FALSE_;
	    PyObject *list;
	    if(!(list=get_list(self))) Py_RETURN_TRUE_;
	    int ret=PyObject_Compare(list, value);
	    Py_DECREF(list);
	    if(ret) Py_RETURN_TRUE_; else Py_RETURN_FALSE_;
	}

    default:
	Py_INCREF(Py_NotImplemented);
	return Py_NotImplemented;
    }
}
